package dataspace

import (
	"bufio"
	"bytes"
	"errors"
	"os"
	"sync"

	"github.com/zeebo/xxh3"
)

const (
	Asc uint8 = iota
	Desc
)

var (
	ErrKeyNotFound    = errors.New("key not found")
	ErrKeyNil         = errors.New("key is nil")
	ErrKeyMissing     = errors.New("key is empty")
	ErrValueNil       = errors.New("value is nil")
	ErrValueMissing   = errors.New("value is empty")
	ErrIncorrectOrder = errors.New("incorrect order")
)

type Dataspace struct {
	mutex      *sync.RWMutex // мьютекс
	path       string        // путь wal-файла
	wal        *os.File      // wal-файл
	writer     *bufio.Writer // запись в wal-файл
	tree       *avltree      // сбалансированное дерево
	list       *list         // двусвязный список
	operations uint          // количество операций
	config     *Config       // конфигурация
}

type Config struct {
	Vacuum           bool // удаление неактуальных данных из файла
	VacuumOperations uint // количество операций вставки/удаления до перезаписи файла
}

type Info struct {
	FileName     string // имя файла
	FileSize     int64  // размер файла
	Length       int64  // количество элементов
	LastModified string // время последнего изменения
}

// Конструктор нового пространства данных.
func New(path string, c *Config) (*Dataspace, error) {
	w, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600)
	if err != nil {
		return nil, err
	}
	ds := &Dataspace{
		mutex:      &sync.RWMutex{},
		path:       path,
		wal:        w,
		writer:     bufio.NewWriter(w),
		tree:       newTree(),
		list:       newList(),
		operations: 0,
		config:     c,
	}
	s, err := w.Stat()
	if err != nil {
		return nil, err
	}
	b := make([]byte, s.Size())
	if _, err := w.Read(b); err != nil {
		return nil, err
	}
	for _, f := range decode(b) {
		if f.s {
			if err := ds.Set(f.k, f.v); err != nil {
				return nil, err
			}
		} else {
			if err := ds.Delete(f.k); err != nil {
				continue
			}
		}
	}
	if err := ds.Vacuum(); err != nil {
		return nil, err
	}
	return ds, nil
}

// Метод записывает данные в пространство данных.
func (ds *Dataspace) Set(k, v []byte) error {
	switch {
	case k == nil:
		return ErrKeyNil
	case len(k) == 0:
		return ErrKeyMissing
	case v == nil:
		return ErrValueNil
	case len(v) == 0:
		return ErrValueMissing
	}
	ds.mutex.Lock()
	defer ds.mutex.Unlock()
	if ds.config.Vacuum && ds.operations == ds.config.VacuumOperations {
		if err := ds.Vacuum(); err != nil {
			return err
		}
		ds.operations = 0
	} else {
		ds.operations++
	}
	h := xxh3.Hash(k)
	if node := ds.tree.search(h); node != nil {
		if bytes.Equal(node.value, v) {
			return nil
		}
		ds.tree.add(h, k, v, node.element)
	} else {
		ds.tree.add(h, k, v, ds.list.push(h))
	}
	data := encode(&fraction{
		m: 1,
		s: true,
		k: k,
		v: v,
	})
	if _, err := ds.writer.Write(data); err != nil {
		return err
	}
	return ds.writer.Flush()
}

// Метод получает данные из пространства данных.
func (ds *Dataspace) Get(k []byte) ([]byte, error) {
	switch {
	case k == nil:
		return nil, ErrKeyNil
	case len(k) == 0:
		return nil, ErrKeyMissing
	}
	ds.mutex.RLock()
	defer ds.mutex.RUnlock()
	if node := ds.tree.search(xxh3.Hash(k)); node != nil {
		return node.value, nil
	}
	return nil, ErrKeyNotFound
}

// Метод удаляет данные из пространства данных.
func (ds *Dataspace) Delete(k []byte) error {
	switch {
	case k == nil:
		return ErrKeyNil
	case len(k) == 0:
		return ErrKeyMissing
	}
	ds.mutex.Lock()
	defer ds.mutex.Unlock()
	if ds.config.Vacuum && ds.operations == ds.config.VacuumOperations {
		if err := ds.Vacuum(); err != nil {
			return err
		}
		ds.operations = 0
	} else {
		ds.operations++
	}
	h := xxh3.Hash(k)
	if node := ds.tree.search(h); node != nil {
		ds.list.remove(node.element)
	} else {
		return ErrKeyNotFound
	}
	ds.tree.remove(h)
	data := encode(&fraction{
		m: 1,
		s: false,
		k: k,
		v: []byte{},
	})
	if _, err := ds.writer.Write(data); err != nil {
		return err
	}
	return ds.writer.Flush()
}

/*
Метод отдаёт указанное в аргументе количество значений в порядке добавления.
Если в качестве аргумента указан 0 - итерация будет по всем элементам.
Если значение больше чем количество элементов - итерация так же будет
по всем элементам.
*/
func (ds *Dataspace) Range(limit, offset uint, orderBy uint8) ([][]byte, error) {
	if orderBy != Asc && orderBy != Desc {
		return nil, ErrIncorrectOrder
	}
	if limit == 0 || limit > ds.list.Len {
		limit = ds.list.Len
	}
	values := make([][]byte, 0, limit)
	switch orderBy {
	case Asc:
		for e := ds.list.front(); e != nil; e = e.nextElement() {
			if offset != 0 {
				offset--
				continue
			}
			if node := ds.tree.search(e.value); node != nil {
				values = append(values, node.value)
			} else {
				return nil, ErrKeyNotFound
			}
			limit--
			if limit == 0 {
				break
			}
		}
	case Desc:
		for e := ds.list.back(); e != nil; e = e.prevElement() {
			if offset != 0 {
				offset--
				continue
			}
			if node := ds.tree.search(e.value); node != nil {
				values = append(values, node.value)
			} else {
				return nil, ErrKeyNotFound
			}
			limit--
			if limit == 0 {
				break
			}
		}
	}
	return values, nil
}

// Метод возвращает информацию о пространстве данных.
func (ds *Dataspace) Info() (*Info, error) {
	i, err := ds.wal.Stat()
	if err != nil {
		return nil, err
	}
	return &Info{
		FileName:     i.Name(),
		FileSize:     i.Size(),
		Length:       int64(ds.list.Len),
		LastModified: i.ModTime().Format("02.01.06 15:04"),
	}, nil
}

// Метод пишет баланс дерева в консоль для визуального анализа.
func (ds *Dataspace) PrintAVLTreeBalance() {
	printTree(ds.tree.root, 0)
}

// Метод закрывает пространство данных.
func (ds *Dataspace) Close() error {
	if err := ds.wal.Sync(); err != nil {
		return err
	}
	return ds.wal.Close()
}
